// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/debug/debug-stack-trace-iterator.h"

#include "src/api-inl.h"
#include "src/debug/debug-evaluate.h"
#include "src/debug/debug-scope-iterator.h"
#include "src/debug/debug.h"
#include "src/debug/liveedit.h"
#include "src/frames-inl.h"
#include "src/isolate.h"

namespace v8 {

std::unique_ptr<debug::StackTraceIterator> debug::StackTraceIterator::Create(
    v8::Isolate* isolate, int index)
{
    return std::unique_ptr<debug::StackTraceIterator>(
        new internal::DebugStackTraceIterator(
            reinterpret_cast<internal::Isolate*>(isolate), index));
}

namespace internal {

    DebugStackTraceIterator::DebugStackTraceIterator(Isolate* isolate, int index)
        : isolate_(isolate)
        , iterator_(isolate, isolate->debug()->break_frame_id())
        , is_top_frame_(true)
    {
        if (iterator_.done())
            return;
        std::vector<FrameSummary> frames;
        iterator_.frame()->Summarize(&frames);
        inlined_frame_index_ = static_cast<int>(frames.size());
        Advance();
        for (; !Done() && index > 0; --index)
            Advance();
    }

    DebugStackTraceIterator::~DebugStackTraceIterator() = default;

    bool DebugStackTraceIterator::Done() const { return iterator_.done(); }

    void DebugStackTraceIterator::Advance()
    {
        while (true) {
            --inlined_frame_index_;
            for (; inlined_frame_index_ >= 0; --inlined_frame_index_) {
                // Omit functions from native and extension scripts.
                if (FrameSummary::Get(iterator_.frame(), inlined_frame_index_)
                        .is_subject_to_debugging()) {
                    break;
                }
                is_top_frame_ = false;
            }
            if (inlined_frame_index_ >= 0) {
                frame_inspector_.reset(new FrameInspector(
                    iterator_.frame(), inlined_frame_index_, isolate_));
                break;
            }
            is_top_frame_ = false;
            frame_inspector_.reset();
            iterator_.Advance();
            if (iterator_.done())
                break;
            std::vector<FrameSummary> frames;
            iterator_.frame()->Summarize(&frames);
            inlined_frame_index_ = static_cast<int>(frames.size());
        }
    }

    int DebugStackTraceIterator::GetContextId() const
    {
        DCHECK(!Done());
        Handle<Object> context = frame_inspector_->GetContext();
        if (context->IsContext()) {
            Object value = Context::cast(*context)->native_context()->debug_context_id();
            if (value->IsSmi())
                return Smi::ToInt(value);
        }
        return 0;
    }

    v8::MaybeLocal<v8::Value> DebugStackTraceIterator::GetReceiver() const
    {
        DCHECK(!Done());
        if (frame_inspector_->IsJavaScript() && frame_inspector_->GetFunction()->shared()->kind() == kArrowFunction) {
            // FrameInspector is not able to get receiver for arrow function.
            // So let's try to fetch it using same logic as is used to retrieve 'this'
            // during DebugEvaluate::Local.
            Handle<JSFunction> function = frame_inspector_->GetFunction();
            Handle<Context> context(function->context(), isolate_);
            // Arrow function defined in top level function without references to
            // variables may have NativeContext as context.
            if (!context->IsFunctionContext())
                return v8::MaybeLocal<v8::Value>();
            ScopeIterator scope_iterator(isolate_, frame_inspector_.get(),
                ScopeIterator::COLLECT_NON_LOCALS);
            // We lookup this variable in function context only when it is used in arrow
            // function otherwise V8 can optimize it out.
            if (!scope_iterator.GetNonLocals()->Has(isolate_,
                    isolate_->factory()->this_string()))
                return v8::MaybeLocal<v8::Value>();
            DisallowHeapAllocation no_gc;
            VariableMode mode;
            InitializationFlag flag;
            MaybeAssignedFlag maybe_assigned_flag;
            int slot_index = ScopeInfo::ContextSlotIndex(
                context->scope_info(), ReadOnlyRoots(isolate_->heap()).this_string(),
                &mode, &flag, &maybe_assigned_flag);
            if (slot_index < 0)
                return v8::MaybeLocal<v8::Value>();
            Handle<Object> value = handle(context->get(slot_index), isolate_);
            if (value->IsTheHole(isolate_))
                return v8::MaybeLocal<v8::Value>();
            return Utils::ToLocal(value);
        }
        Handle<Object> value = frame_inspector_->GetReceiver();
        if (value.is_null() || (value->IsSmi() || !value->IsTheHole(isolate_))) {
            return Utils::ToLocal(value);
        }
        return v8::MaybeLocal<v8::Value>();
    }

    v8::Local<v8::Value> DebugStackTraceIterator::GetReturnValue() const
    {
        DCHECK(!Done());
        if (frame_inspector_ && frame_inspector_->IsWasm()) {
            return v8::Local<v8::Value>();
        }
        bool is_optimized = iterator_.frame()->is_optimized();
        if (is_optimized || !is_top_frame_ || !isolate_->debug()->IsBreakAtReturn(iterator_.javascript_frame())) {
            return v8::Local<v8::Value>();
        }
        return Utils::ToLocal(isolate_->debug()->return_value_handle());
    }

    v8::Local<v8::String> DebugStackTraceIterator::GetFunctionDebugName() const
    {
        DCHECK(!Done());
        return Utils::ToLocal(frame_inspector_->GetFunctionName());
    }

    v8::Local<v8::debug::Script> DebugStackTraceIterator::GetScript() const
    {
        DCHECK(!Done());
        Handle<Object> value = frame_inspector_->GetScript();
        if (!value->IsScript())
            return v8::Local<v8::debug::Script>();
        return ToApiHandle<debug::Script>(Handle<Script>::cast(value));
    }

    debug::Location DebugStackTraceIterator::GetSourceLocation() const
    {
        DCHECK(!Done());
        v8::Local<v8::debug::Script> script = GetScript();
        if (script.IsEmpty())
            return v8::debug::Location();
        return script->GetSourceLocation(frame_inspector_->GetSourcePosition());
    }

    v8::Local<v8::Function> DebugStackTraceIterator::GetFunction() const
    {
        DCHECK(!Done());
        if (!frame_inspector_->IsJavaScript())
            return v8::Local<v8::Function>();
        return Utils::ToLocal(frame_inspector_->GetFunction());
    }

    std::unique_ptr<v8::debug::ScopeIterator>
    DebugStackTraceIterator::GetScopeIterator() const
    {
        DCHECK(!Done());
        StandardFrame* frame = iterator_.frame();
        if (frame->is_wasm_interpreter_entry()) {
            return std::unique_ptr<v8::debug::ScopeIterator>(new DebugWasmScopeIterator(
                isolate_, iterator_.frame(), inlined_frame_index_));
        }
        return std::unique_ptr<v8::debug::ScopeIterator>(
            new DebugScopeIterator(isolate_, frame_inspector_.get()));
    }

    bool DebugStackTraceIterator::Restart()
    {
        DCHECK(!Done());
        if (iterator_.is_wasm())
            return false;
        return !LiveEdit::RestartFrame(iterator_.javascript_frame());
    }

    v8::MaybeLocal<v8::Value> DebugStackTraceIterator::Evaluate(
        v8::Local<v8::String> source, bool throw_on_side_effect)
    {
        DCHECK(!Done());
        Handle<Object> value;
        i::SafeForInterruptsScope safe_for_interrupt_scope(isolate_);
        if (!DebugEvaluate::Local(isolate_, iterator_.frame()->id(),
                inlined_frame_index_, Utils::OpenHandle(*source),
                throw_on_side_effect)
                 .ToHandle(&value)) {
            isolate_->OptionalRescheduleException(false);
            return v8::MaybeLocal<v8::Value>();
        }
        return Utils::ToLocal(value);
    }
} // namespace internal
} // namespace v8
